#include "stdafx.h"
#include "Mouse.h"
using namespace Reign::Core;

namespace Reign
{namespace Input
{namespace DI
{
	//Buffer object for getting a mouse state
	struct MouseState
	{
		long X;
		long Y;
		long Z;
		char Buttons[3];
	};

	#pragma region Properties
	Vector2 Mouse::Location::get() {return location;}
	Vector2 Mouse::ScreenLocation::get() {return screenLocation;}
	Vector2 Mouse::Velocity::get() {return velocity;}
	Vector3 Mouse::ScrollWheelVelocity::get() {return scrollWheelVelocity;}
	Button^ Mouse::Left::get() {return left;}
	Button^ Mouse::Right::get() {return right;}
	Button^ Mouse::Middle::get() {return middle;}
	#pragma endregion
	
	#pragma region Constructors
	Mouse::Mouse(DisposableI^ parent)
	: Disposable(parent)
	{
		null();
		try
		{
			input = parent->FindParentOrSelfWithException<Input^>();

			left = gcnew Button();
			right = gcnew Button();
			middle = gcnew Button();

			IDirectInputDevice8* deviceTEMP;
			if (FAILED(input->Device->CreateDevice(GUID_SysMouse, &deviceTEMP, 0)))
			{
				device = 0;
				throw(gcnew Exception("Reign.Input.DI.Mouse: Failed at pInput->Device()->CreateDevice"));
			}
			device = deviceTEMP;

			DIOBJECTDATAFORMAT objectDataFormats[] =
			{
				{&GUID_XAxis, FIELD_OFFSET(MouseState, X), DIDFT_AXIS|DIDFT_ANYINSTANCE, 0},
				{&GUID_YAxis, FIELD_OFFSET(MouseState, Y), DIDFT_AXIS|DIDFT_ANYINSTANCE, 0},
				{&GUID_ZAxis, FIELD_OFFSET(MouseState, Z), DIDFT_AXIS|DIDFT_ANYINSTANCE|DIDFT_OPTIONAL, 0},
				{0, FIELD_OFFSET(MouseState, Buttons[0]), DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0},
				{0, FIELD_OFFSET(MouseState, Buttons[1]), DIDFT_BUTTON|DIDFT_ANYINSTANCE|DIDFT_OPTIONAL, 0},
				{0, FIELD_OFFSET(MouseState, Buttons[2]), DIDFT_BUTTON|DIDFT_ANYINSTANCE|DIDFT_OPTIONAL, 0}
			};

			DIDATAFORMAT dataFormat =
			{
				sizeof(DIDATAFORMAT),
				sizeof(DIOBJECTDATAFORMAT),
				DIDF_ABSAXIS,
				sizeof(MouseState),
				(sizeof(objectDataFormats) / sizeof(DIOBJECTDATAFORMAT)),
				objectDataFormats
			};

			if (FAILED(device->SetDataFormat(&dataFormat))) throw(gcnew Exception("Reign.Input.DI.Mouse: Failed at device->SetDataFormat"));
			if (FAILED(device->SetCooperativeLevel((HWND)input->handle.ToPointer(), DISCL_NONEXCLUSIVE|DISCL_FOREGROUND))) throw(gcnew Exception("Reign.Input.DI.Mouse: Failed at device->SetCooperativeLevel"));

			input->UpdateCallback += gcnew Input::UpdateCallbackFunc(this, &Mouse::Update);
		}
		catch (Exception^ ex)
		{
			delete this;
			throw ex;
		}
	}

	Mouse::~Mouse()
	{
		disposeChilderen();
		if (device)
		{
			if (input) input->UpdateCallback -= gcnew Input::UpdateCallbackFunc(this, &Mouse::Update);
			device->Unacquire();
			device->Release();
		}
		null();
	}

	void Mouse::null()
	{
		device = 0;
		input = nullptr;
	}
	#pragma endregion
	
	#pragma region Methods
	void Mouse::Update()
	{
		if (FAILED(device->Poll()))
		{
			while (device->Acquire() == DIERR_INPUTLOST);
		}
		MouseState ms;
		device->GetDeviceState(sizeof(MouseState), &ms);

		left->Update(ms.Buttons[0]);
		right->Update(ms.Buttons[1]);
		middle->Update(ms.Buttons[2]);

		System::Drawing::Point cursor = System::Windows::Forms::Cursor::Position;
		location = Vector2(cursor.X, cursor.Y);

		velocity = Vector2((ms.X-velocityPosLastX), (ms.Y-velocityPosLastY));
		scrollWheelVelocity = Vector3(0, 0, (ms.Z-lastScrollWheelVelocity)/120.0f);

		velocityPosLastX = ms.X;
		velocityPosLastY = ms.Y;
		lastScrollWheelVelocity = ms.Z;
	}
	#pragma endregion
}
}
}